/*
 * Copyright (C) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "accessibility_espeak_proxy.h"
#include "accessibility_espeak_player.h"

#include "utils.h"

#ifndef ESPEAK_TAG
#define ESPEAK_TAG "ESPEAK_TAG| "
#endif

#ifndef PERFORMANCE_TEST
#define PERFORMANCE_TEST "PERFORMANCE_TEST "
#endif

namespace OHOS {
namespace Accessibility {

const std::string EspeakProxy::lan_[LANGUAGE_NUM] = {"zh", "zh-yue", "en"};
const std::string EspeakProxy::v_[VOICE_NUM] = {"f", "m", "croak", "whisper"};
int EspeakProxy::currentLanguage_ = 0;
int EspeakProxy::currentVoice_ = 0;
int EspeakProxy::currentPitch_ = 2;

void EspeakProxy::Init()
{
    HILOG_DEBUG(ESPEAK_TAG "EspeakProxy Init");
    //初始化espeak
    int samplerate=espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 0, ESPEAK_DATA_PATH, 0);
    if(samplerate != 22050){
        HILOG_DEBUG(ESPEAK_TAG "espeak_Initialize samplerate:%{public}d", samplerate);
        if(samplerate < 0){
            HILOG_ERROR(ESPEAK_TAG "espeak_Initialize failed");
            return;
        }
        Singleton<EspeakPlayer>::GetInstance().SetParams(1, samplerate);
    }

    //默认中文
    if(!SetVoiceByName(0, 0)){
        HILOG_ERROR("espeak_SetVoiceByName failed");
        return;
    }

    //设置默认语速和音量
    if(!SetSpeed()){
        HILOG_ERROR("espeak_SetSpeed failed");
        return;
    }
    if(!SetVolume()){
        HILOG_ERROR("espeak_SetVolume failed");
        return;
    }

    //设置回调函数
    espeak_SetSynthCallback(SynthCallback);

    HILOG_DEBUG(ESPEAK_TAG "EspeakProxy init success");
}

void EspeakProxy::Release()
{
    HILOG_DEBUG(ESPEAK_TAG "EspeakProxy Release");
    WaitSynth(); // 释放资源时先等待合成结束
    Terminate();
    HILOG_DEBUG(ESPEAK_TAG "EspeakProxy Release success");
}

bool EspeakProxy::Synthesize(const char *text)
{
    HILOG_DEBUG(ESPEAK_TAG "Synthesize text:%{public}s", text);
    int64_t start_time = Utils::GetSystemTime();
    int64_t end_time = 0;
    if(text == nullptr){
        HILOG_DEBUG(ESPEAK_TAG "Synthesize text is null");
        return false;
    }

    //判断espeak是否正在合成
    if(IsPlaying()){
        HILOG_DEBUG(ESPEAK_TAG "Synthesize espeak is playing");
        if(!Cancel()){
            HILOG_DEBUG(ESPEAK_TAG "Synthesize cancel failed");
            return false;
        }
    }

    //判断espeak队列是否为空
    auto &ep = Singleton<EspeakPlayer>::GetInstance();
    if(ep.IsPlaying()){
        HILOG_DEBUG(ESPEAK_TAG "espeak is playing, queue is not empty");
        ep.Interrupt();
    }

    int ret = espeak_Synth(text, strlen(text) + 1, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL, NULL);
    end_time = Utils::GetSystemTime();
    HILOG_DEBUG(PERFORMANCE_TEST "Synthesize cost time: %{public}lld ms", end_time - start_time);
    HILOG_DEBUG(ESPEAK_TAG "espeak_Synth ret: %{public}d", ret);
    return ret == 0;
}

bool EspeakProxy::SetSpeed(int speed)
{
    HILOG_DEBUG(ESPEAK_TAG "SetSpeed speed:%{public}d", speed);
    if (speed < 30 || speed > 100) {
        HILOG_DEBUG(ESPEAK_TAG "SetSpeed speed out of range");
        return false;
    }
    // 把30-100的speed等比例转化为80-450，因为espeak的语速范围是80-450
    int actualSpeed = (speed - 30) * (450 - 80) / (100 - 30) + 80;
    // 设置实际的语速值
    int ret = espeak_SetParameter(espeakRATE, actualSpeed, 0);
    return ret == 0;
}

bool EspeakProxy::SetVolume(int volume)
{
    HILOG_DEBUG(ESPEAK_TAG "SetVolume volume:%{public}d", volume);
    if (volume < 0 || volume > 100) {
        HILOG_DEBUG(ESPEAK_TAG "SetVolume volume out of range");
        return false;
    }
    int ret = espeak_SetParameter(espeakVOLUME, volume, 0);
    return ret == 0;
}

bool EspeakProxy::SetBasePitch(int pitch)
{
    HILOG_DEBUG(ESPEAK_TAG "SetBasePitch pitch:%{public}d", pitch);
    if (pitch < 0 || pitch > 100) {
        HILOG_DEBUG(ESPEAK_TAG "SetBasePitch pitch out of range");
        return false;
    }
    int ret = espeak_SetParameter(espeakPITCH, pitch, 0);
    return ret == 0;
}

bool EspeakProxy::SetPitchRange(int range)
{
    HILOG_DEBUG(ESPEAK_TAG "SetPitchRange range:%{public}d", range);
    if (range < 0 || range > 100) {
        HILOG_DEBUG(ESPEAK_TAG "SetPitchRange range out of range");
        return false;
    }
    int ret = espeak_SetParameter(espeakRANGE, range, 0);
    return ret == 0;
}

bool EspeakProxy::QuickSetPitch(int option)
{
    if(option < 0 || option > 2){
        HILOG_DEBUG(ESPEAK_TAG "QuickSetPitch option out of range");
        return false;
    }
    switch(option){
        case 0:
            return SetBasePitch(50) && SetPitchRange(50);
        case 1:
            return SetBasePitch(70) && SetPitchRange(80);
        case 2:
            return SetBasePitch(30) && SetPitchRange(10);
        default:
            return false;
    }
}

bool EspeakProxy::CheckPitch(int voice, int pitch)
{
    if(voice == 0){
        return pitch >= 1 && pitch <= 4; // 1-4
    }else if(voice == 1){
        return pitch >= 1 && pitch <= 7; // 1-7
    }else{
        return false;
    }
}

bool EspeakProxy::SetVoiceByName(int language, int voice, int pitch)
{
    HILOG_DEBUG(ESPEAK_TAG "SetVoiceByName language:%{public}d, voice:%{public}d, pitch:%{public}d", language, voice, pitch);
    if(language < 0 || language > LANGUAGE_NUM - 1){
        HILOG_DEBUG(ESPEAK_TAG "SetVoiceByName language out of range");
        return false;
    }
    if(voice < 0 || voice > VOICE_NUM - 1){
        HILOG_DEBUG(ESPEAK_TAG "SetVoiceByName voice out of range");
        return false;
    }
    std::string voiceName = lan_[language] + "+" + v_[voice];
    if(CheckPitch(voice, pitch)){
        voiceName += std::to_string(pitch);
    }else if(voice < 2){
        // 当pitch不符合要求时，若voice为0或1，则默认为pitch=2
        voiceName += "2";
    }
    HILOG_DEBUG(ESPEAK_TAG "SetVoiceByName voiceName:%{public}s", voiceName.c_str());
    int ret = espeak_SetVoiceByName(voiceName.c_str());
    if(ret == 0){
        HILOG_DEBUG(ESPEAK_TAG "SetVoiceByName success");
        currentLanguage_ = language;
        currentVoice_ = voice;
        currentPitch_ = pitch;
        return true;
    }
    return false;
}

bool EspeakProxy::NextLanguage(int direction)
{
    HILOG_DEBUG(ESPEAK_TAG "NextLanguage direction:%{public}d", direction);
    if(direction != -1 && direction != 1){
        HILOG_DEBUG(ESPEAK_TAG "NextLanguage direction out of range");
        return false;
    }
    int nextLanguage = (currentLanguage_ + direction) % LANGUAGE_NUM;
    if(nextLanguage < 0){
        nextLanguage += LANGUAGE_NUM;
    }
    bool ret = SetVoiceByName(nextLanguage, currentVoice_, currentPitch_);
    if(ret){
        if(currentLanguage_ == 2){
            TestSpeakEnglish(); //测试英文
        }else{
            TestSpeakChinese(); //测试中文
        }
    }
    HILOG_DEBUG(ESPEAK_TAG "NextLanguage ret:%{public}d", ret);
    return ret;
}

bool EspeakProxy::NextVoice(int direction)
{
    HILOG_DEBUG(ESPEAK_TAG "NextVoice direction:%{public}d", direction);
    if(direction != -1 && direction != 1){
        HILOG_DEBUG(ESPEAK_TAG "NextVoice direction out of range");
        return false;
    }
    int nextVoice = (currentVoice_ + direction) % VOICE_NUM;
    if(nextVoice < 0){
        nextVoice += VOICE_NUM;
    }
    bool ret = SetVoiceByName(currentLanguage_, nextVoice, currentPitch_);
    if(ret){
        if(currentLanguage_ == 2){
            TestSpeakEnglish(); //测试英文
        }else{
            TestSpeakChinese(); //测试中文
        }
    }
    HILOG_DEBUG(ESPEAK_TAG "NextVoice ret:%{public}d", ret);
    return ret;
}

int EspeakProxy::SynthCallback(short *wav, int numsamples, espeak_EVENT *events)
{
    HILOG_DEBUG(ESPEAK_TAG "SynthCallback numsamples:%{public}d", numsamples);
    int64_t start_time = Utils::GetSystemTime();
    int64_t end_time;
    //wav=null则最后一次callback
    if(wav == nullptr ){
        HILOG_DEBUG(ESPEAK_TAG "SynthCallback wav is null");
        Singleton<EspeakPlayer>::GetInstance().WriteToQueue(wav, -1); //将null，-1入队表示单次合成结束
        return 0;
    }
    if(numsamples <= 0){
        HILOG_DEBUG(ESPEAK_TAG "SynthCallback numsamples <= 0");
        return 0;
    }
    Singleton<EspeakPlayer>::GetInstance().WriteToQueue(wav, numsamples);
    end_time = Utils::GetSystemTime();
    HILOG_DEBUG(PERFORMANCE_TEST "SynthCallback cost time: %{public}lld ms", end_time - start_time);
    return 0;
}

} // namespace Accessibility
} // namespace OHOS